package com.demo.test.util;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.demo.test.entity.JarMethodDefinition;
import com.demo.test.entity.MethodParameterPo;
import com.test.interfaces.parentface.JarRuleParentFace;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * 功能概述:
 *
 * @author maoxiaomeng
 * @date 2022/6/10 10:47
 */
@UtilityClass
public class JarRuleUtil {

    public Logger log = LoggerFactory.getLogger(JarRuleUtil.class);

    /**
     * Map<filePath,List<Class<?>>>
     */
    static Map<String, List<Class<?>>> classMap = new HashMap<>();

    ClassLoader jarPackageLoader;

    /**
     * 注解的全限定类名,以及注解class;使用这里的注解,主要是因为 类加载器不同,无法获取到解析的class的系统注解
     */
    static Map<String, Class<? extends Annotation>> annotationMapping = new HashMap<>();
    /**
     * Map<filePath,List<JarMethodDefinition>>
     */
    static Map<String, List<JarMethodDefinition>> definitionMap = new HashMap<>();

    static Map<Class<?>, ParameterizedTypeImpl> parameterizedTypeMap = new HashMap<>();

    public static void main(String[] args) {
        String filePath = "C:\\Users\\maoxiaomeng\\IdeaProjects\\dynamic-load-jar-demo\\testB\\target\\testB-1.0-SNAPSHOT.jar";
        List<JarMethodDefinition> jarMethodDefinitions = loadFile(filePath);
        //System.out.println("jarMethodDefinitions = " + JSONUtil.toJsonPrettyStr(jarMethodDefinitions));

        JarRuleHelper start = JarRuleHelper.start(filePath).parseDefinition();
        List<JarMethodDefinition> definitionList = start.getDefinitionList();
        System.out.println("jarMethodDefinitions = " + JSONUtil.toJsonPrettyStr(definitionList));
    }


    //1.获取jar文件,转换为file
    //2.加载jar文件,获取所有实现了指定接口的实现类,按照 包路径-className 组装好
    //3.解析class 的 所有的方法 结构 不入库
    //4.模拟调用
    @SneakyThrows
    public static List<JarMethodDefinition> loadFile(String filePath) {
        // 创建URL数组，指定JAR包的路径
        File jarFile = new File(filePath);
        URL[] urls = new URL[]{jarFile.toURI().toURL()};
        // 创建URLClassLoader实例，指定父ClassLoader为系统类加载器

        ClassLoader classLoader = JarRuleUtil.class.getClassLoader();
        log.info("use {} as parent classloader", classLoader.getClass().getName());
        jarPackageLoader = new URLClassLoader(urls, classLoader);

        List<JarMethodDefinition> parentApiPos = new ArrayList<>();
        Map<String, List<String>> packageMapping = loadJar(filePath);
        Class<?> superClass = JarRuleParentFace.class;
        Predicate<Class<?>> filter = clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz);

        //扫描指定包路径下的所有实现类
        Set<Class<?>> classes = new HashSet<>(packageMapping.size());
        log.info("当前使用的类加载器为:{}", jarPackageLoader.getClass().getName());
        packageMapping.forEach((basePackage, value) -> {
            if (jarPackageLoader != null) {
                List<? extends Class<?>> collect = value.stream().map(clazzName -> {
                    try {
                        return jarPackageLoader.loadClass(clazzName);

                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        throw new RuntimeException("classNotFound:" + clazzName);
                    }
                }).filter(filter).collect(Collectors.toList());
                classes.addAll(collect);
            }
        });

        for (Class<?> aClass : classes) {
            String className = aClass.getName();
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                //解析方法
                JarMethodDefinition parentApiPo = parseDefinition(declaredMethod);
                parentApiPo.setClassName(className);
                parentApiPos.add(parentApiPo);
            }
        }

        classMap.put(filePath, new ArrayList<>(classes));
        definitionMap.put(filePath, parentApiPos);
        return parentApiPos;
    }

    /**
     * 基于路径 1.本地路径 2.url路径
     *
     * @param jarPath 路径
     * @throws IOException io异常
     */
    public static Map<String, List<String>> loadJar(String jarPath) throws IOException {
        File jarFile = new File(jarPath);
        JarFile jar = new JarFile(jarFile);
        Map<String, List<String>> stringListMap = parsePackages(jar);
        // 从URLClassLoader类中获取类所在文件夹的方法，jar也可以认为是一个文件夹
        Method method = null;
        try {
            method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        } catch (NoSuchMethodException | SecurityException e1) {
            e1.printStackTrace();
        }
        //获取方法的访问权限以便写回
        assert method != null;
        boolean accessible = method.isAccessible();
        try {
            method.setAccessible(true);
            // 获取系统类加载器
            URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            URL url = jarFile.toURI().toURL();
            method.invoke(classLoader, url);
            // jarPackageLoader = classLoader;
        } catch (Exception e) {
            throw new RuntimeException("加载jar包失败:" + e.getMessage());
        } finally {
            method.setAccessible(accessible);
        }
        return stringListMap;
    }

    public static Map<String, List<String>> parsePackages(JarFile jarFile) {
        Enumeration<JarEntry> entries = jarFile.entries();
        List<String> files = new ArrayList<>();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            if (!jarEntry.getName().contains("META-INF")) {

                String className = jarEntry.getName();

                if (className.endsWith(".class")) {
                    className = className.substring(0, className.length() - 6).replace("/", ".");
                    files.add(className);
                }
            }
        }
        return handleBasePackage(files);
    }

    //[com.rule.jar.TestJar, com.rule2.jar.TestJar2, com.TestJar3,cn.Test4]
    //com,cn
    public Map<String, List<String>> handleBasePackage(List<String> classes) {
        //获取所有的class路径
        Set<String> collect = classes.stream().map(e -> e.substring(0, e.lastIndexOf(".")) + StrUtil.DOT)
                .collect(Collectors.toSet());
        Map<String, List<String>> packageMap = new HashMap<>(collect.size());

        collect.forEach(e -> {
            packageMap.put(e, classes.stream().filter(clz -> clz.startsWith(e)).collect(Collectors.toList()));
        });
        return packageMap;
    }

    /**
     * 从Method获取结构
     *
     * @param method 方法
     * @return 结构化对象
     */
    public static JarMethodDefinition parseDefinition(Method method) {
        JarMethodDefinition parentApiPo = new JarMethodDefinition();
        parentApiPo.setMethod(method.getName());
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation annotation = method.getAnnotation(ApiOperation.class);
            parentApiPo.setMethodName(annotation.value());
            parentApiPo.setDesc(annotation.notes());
        }
        //入参出参
        Parameter[] parameters = method.getParameters();
        parseParameter(parameters, parentApiPo);
        Type genericReturnType = method.getGenericReturnType();
        MethodParameterPo methodParameterPo = parseClass(genericReturnType);
        parentApiPo.setRes(genericReturnType);
        parentApiPo.setResponse(methodParameterPo);
        return parentApiPo;
    }


    public static void parseParameter(Parameter[] parameters, JarMethodDefinition parentApiPo) {
        List<MethodParameterPo> requestList = ListUtil.toList();
        for (Parameter parameter : parameters) {
            MethodParameterPo methodParameterPo = new MethodParameterPo();
            String name = parameter.getName();
            if (parameter.isAnnotationPresent(ApiParam.class)) {
                ApiParam annotation = parameter.getAnnotation(ApiParam.class);
                methodParameterPo.setInfo(annotation.value());
                methodParameterPo.setNecessary(annotation.required());
            }
            methodParameterPo.setType(parameter.getType());
            methodParameterPo.setKey(name);
            requestList.add(methodParameterPo);
        }
        MethodParameterPo root = JarRuleHelper.buildRoot();
        root.setChildren(requestList);
        parentApiPo.setRequest(root);
    }

    public static boolean isBasicTypeClass(Class<?> clazz) {
        return ClassUtil.isBasicType(clazz) || clazz.equals(String.class);
    }


    private static MethodParameterPo parseClass(Type type) {
        Class<?> clazz = null;
        try {
            clazz = (Class<?>) type;
        } catch (ClassCastException e) {
            if (type instanceof ParameterizedTypeImpl) {
                ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl) type;
                Class<?> rawType = parameterizedType.getRawType();
                Type genericType;
                if (rawType.isInterface()) {
                    genericType = rawType.getGenericInterfaces()[0];
                    ParameterizedTypeImpl genericInterface1 = (ParameterizedTypeImpl) genericType;
                    Class<?> rawType1 = genericInterface1.getRawType();
                    if (!rawType1.equals(Collection.class)) {
                        throw new RuntimeException("暂不支持body的该写法");
                    }
                    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                    Type actualTypeArgument = actualTypeArguments[0];
                    clazz = (Class<?>) actualTypeArgument;
                    MethodParameterPo fields = JarClassUtil.getFields(clazz);
                    MethodParameterPo collection = new MethodParameterPo();
                    collection.setKey(type.getTypeName())
                            .setInfo("Collection")
                            .setAbsoluteKey("[]")
                            .setType(rawType1)
                            .setTypeName(JarClassUtil.convertToDataType(rawType1.getSimpleName()))
                            .setNecessary(true)
                            .setChildren(ListUtil.of(fields));
                    return collection;
                } else {
                    JarClassUtil.parameterizedTypeMap.put(rawType, parameterizedType);
                    return parseClass(rawType);
                }
            }
        }
        return JarClassUtil.getFields(clazz);
    }
}
